#include "TcpClient.h"
#include "EventLoop.h"
#include "Socket.h"
#include "Channel.h"

#include <arpa/inet.h>
#include "util.h"
#include <assert.h>
#include <string.h>

using namespace std::placeholders;

__thread char t_errnobuf[512];
const int TcpClient::kMaxRetryDelayMs;

TcpClient::TcpClient(EventLoop* loop, const string& ip, const int port)
    : loop_(loop)
    , retry_(false)
    , connect_(false)
    , nextConnId_(1)
    , retryDelayMs_(kInitRetryDelayMs)
    , state_(kDisconnected)
    , host_(ip.c_str())
    , port_(port)
{

}

TcpClient::~TcpClient()
{
}

void TcpClient::connect()
{
    start();
}

void TcpClient::start()
{
    connect_ = true;
    loop_->runInLoop(std::bind(&TcpClient::startInLoop, this));
}

void TcpClient::startInLoop()
{
    loop_->assertInLoopThread();
    assert(state_ == kDisconnected);
    if (connect_)
    {
        connectServer();
    }
    else
    {
        // LOG_DEBUG << "do not connect";
    }
}

void TcpClient::restart()
{
    loop_->assertInLoopThread();
    setState(kDisconnected);
    retryDelayMs_ = kInitRetryDelayMs;
    connect_ = true;
    startInLoop();
}

void TcpClient::disconnect()
{
    connect_ = false;
    {
        std::lock_guard<std::mutex> lock(mutex_);
        if (connection_)
        {
            connection_->shutdown();
        }
    }
}

void TcpClient::stop()
{
    connect_ = false;
    loop_->queueInLoop(std::bind(&TcpClient::stopInLoop, this));
}

void TcpClient::newConnection(int sockfd)
{
    loop_->assertInLoopThread();
    char buf[32];
    snprintf(buf, sizeof buf, ":%s#%d", "127.0.0.1", nextConnId_);
    ++nextConnId_;
    string connName = name_ + buf;

    TcpConnectionPtr conn(new TcpConnection(loop_,connName,sockfd));

    conn->setConnectionCallback(connectionCallback_);
    conn->setMessageCallback(messageCallback_);
    conn->setWriteCompleteCallback(writeCompleteCallback_);
    conn->setCloseCallback( std::bind(&TcpClient::removeConnection, this, _1 ));
    {
        std::lock_guard<std::mutex> lock(mutex_);
        connection_ = conn;
    }
    conn->connectEstablished();
}

void TcpClient::removeConnection(const TcpConnectionPtr& conn)
{
    loop_->assertInLoopThread();
    assert(loop_ == conn->getLoop());

    {
        std::lock_guard<std::mutex> lock(mutex_);
        assert(connection_ == conn);
        connection_.reset();
    }

    loop_->queueInLoop(std::bind(&TcpConnection::connectDestroyed, conn));
    if (retry_ && connect_)
    {
        std::cout << "restart\n";
        restart();
    }
}

void TcpClient::connectServer()
{
    int sockfd = createNonblockingSocket();
    setReuseAddr(sockfd, true);
    setKeepAlive(sockfd, true);
    struct sockaddr_in servaddr;
	bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(port_);
    inet_pton(AF_INET, host_, &servaddr.sin_addr);
    int ret = ::connect( sockfd, (struct sockaddr *)&servaddr, sizeof( servaddr ));
    int savedErrno = (ret == 0) ? 0 : errno;
    switch (savedErrno)
    {
        case 0:
        case EINPROGRESS:
        case EINTR:
        case EISCONN:
        // 连接成功
            connecting(sockfd);
            break;

        case EAGAIN:
        case EADDRINUSE:
        case EADDRNOTAVAIL:
        case ECONNREFUSED:
        case ENETUNREACH:
            retry(sockfd);
            break;

        case EACCES:
        case EPERM:
        case EAFNOSUPPORT:
        case EALREADY:
        case EBADF:
        case EFAULT:
        case ENOTSOCK:
            std::cout << "connect error in Connector::startInLoop " << savedErrno << std::endl;
            ::close(sockfd);
            break;

        default:
            std::cout << "Unexpected error in Connector::startInLoop " << savedErrno << std::endl;
            ::close(sockfd);
            // connectErrorCallback_();
            break;
    }
}

void TcpClient::connecting(int sockfd)
{
    setState(kConnecting);
    assert(!channel_);
    channel_.reset(new Channel(loop_, sockfd));
    channel_->setWriteCallback( std::bind(&TcpClient::handleWrite, this));
    channel_->enableWriting();
}

void TcpClient::retry(int sockfd)
{
    close(sockfd);
    setState(kDisconnected);
    if (connect_){
        std::cout << "TcpClient::retry delay : " << retryDelayMs_/1000.0 << std::endl;
        loop_->runAfter(retryDelayMs_/1000.0, std::bind(&TcpClient::startInLoop, this));
        retryDelayMs_ = std::min(retryDelayMs_ * 2, kMaxRetryDelayMs);
    }
    else 
    {
        std::cout << "TcpClient do not connect" << std::endl;
    }
    // loop_->runAfter(retryDelayMs_/1000.0, std::bind(&TcpClient::connectServer, this));
    // retryDelayMs_ = std::min(retryDelayMs_ * 2, kMaxRetryDelayMs);
}

void TcpClient::stopInLoop()
{
    loop_->assertInLoopThread();
    if (state_ == kConnecting)
    {
        setState(kDisconnected);
        channel_->disableAll();
        channel_->remove();
        int sockfd = channel_->fd();
        loop_->queueInLoop(std::bind(&TcpClient::resetChannel, this));
        ::close(sockfd);
    }
}

int TcpClient::removeAndResetChannel()
{
    channel_->disableAll();
    channel_->remove();
    int sockfd = channel_->fd();
    loop_->queueInLoop(std::bind(&TcpClient::resetChannel, this));
    return sockfd;
}

void TcpClient::resetChannel()
{
    channel_.reset();
}


void TcpClient::handleWrite()
{
    if (state_ == kConnecting)
    {
        int sockfd = removeAndResetChannel(); // 取消关注事件
        int err = getSocketError(sockfd);
        if (err)
        {
            std::cout  << "TcpClient::handleWrite - SO_ERROR = "
               << err << " " << strerror_r(err, t_errnobuf, sizeof(t_errnobuf)) << std::endl;
            retry(sockfd);
        }
        else if (isSelfConnect(sockfd))
        {
            std::cout << "TcpClient::handleWrite - Self connect" << std::endl;
            retry(sockfd);
        }
        else
        {
            // 连接成功
            setState(kConnected);
            if (connect_)
            {
                // newConnectionCallback_(sockfd);
                newConnection(sockfd);
            }
            else
            {
                ::close(sockfd);
            }
        }
    }
    else 
    {
        std::cout << "TcpClient::handleWrite kDisconnected" << std::endl;
        assert(state_ == kDisconnected);
    }
    
}

void TcpClient::handleError()
{
    if (state_ == kConnecting)
    {
        int sockfd = removeAndResetChannel();
        int err = getSocketError(sockfd);
        std::cout  << "SO_ERROR = "
               << err << " " << strerror_r(err, t_errnobuf, sizeof(t_errnobuf)) << std::endl;
        retry(sockfd);
    }
    
}
